﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Xms.Abp.Data;

public static class TreesExtensions
{
    #region ToTree
    public static IEnumerable<TTree> ToTree<TTree, TKey>(this IEnumerable<TTree> trees, Func<TTree, TKey> idSelector, Func<TTree, TKey> parentIdSelector)
        where TTree : class, ITree<TTree>
    {
        var dict = new Dictionary<TKey, TTree>();
        var groups = new Dictionary<TKey, List<TTree>>();
        foreach (var item in trees)
        {
            var key = idSelector(item);
            if (!dict.ContainsKey(key))
            {
                dict[key] = item;
            }
            var pkey = parentIdSelector(item);
            if (!groups.ContainsKey(pkey))
            {
                groups[pkey] = new List<TTree>();
            }
            groups[pkey].Add(item);
        }
        //var groups = trees.GroupBy(parentIdSelector);
        IEnumerable<TTree> results = null;
        foreach (var group in groups)
        {
            if (group.Key != null && dict.ContainsKey(group.Key))
            {
                dict[group.Key].Children ??= new List<TTree>();
                dict[group.Key].Children.AddRange(group.Value);
            }
            else if (results == null)
            {
                results = group.Value;
            }
            else
            {
                results = results.Union(group.Value);
            }
        }
        return results;
    }

    public static IEnumerable<TTree> ToStructTree<TTree, TKey>(this IEnumerable<TTree> trees)
        where TTree : class, ITree<TTree>, IStructTree<TTree, TKey>
        where TKey : struct
    {
        return trees.ToTree(r => r.Id, r => r.ParentId);
    }

    public static IEnumerable<TTree> ToCodeTree<TTree, TCode>(this IEnumerable<TTree> trees)
        where TTree : class, ITree<TTree>, ICodeTree<TTree, TCode>
        where TCode : class
    {
        return trees.ToTree(r => r.Code, r => r.ParentCode);
    }

    public static IEnumerable<TTree> ToTree<TTree, TKey>(this IEnumerable<TTree> trees)
        where TTree : class, ITree<TTree>, ITree<TTree, TKey>
        where TKey : class
    {
        return trees.ToTree(r => r.Id, r => r.ParentId);
    }
    #endregion

    #region Contains
    public static bool Contains<TTree, TKey>(this ITree<TTree> tree, Func<TTree, TKey> keySelector, TKey key)
        where TTree : class, ITree<TTree>
    {
        return tree.Contains(k => keySelector.Invoke(k as TTree), key);
    }

    public static bool Contains<TTree, TKey>(this IStructTree<TTree, TKey> tree, TKey key)
        where TTree : class, ITree<TTree>, IStructTree<TTree, TKey>
        where TKey : struct
    {
        return tree.Contains(k => k.Id, key);
    }

    public static bool Contains<TTree, TKey>(this ITree<TTree, TKey> tree, TKey key)
        where TTree : class, ITree<TTree>, ITree<TTree, TKey>
        where TKey : class
    {
        return tree.Contains(k => k.Id, key);
    }

    public static bool Contains<TTree, TCode>(this ICodeTree<TTree, TCode> tree, TCode code)
        where TTree : class, ITree<TTree>, ICodeTree<TTree, TCode>
        where TCode : class
    {
        return tree.Contains(k => k.Code, code);
    }
    #endregion

    public static TTree ForEach<TTree>(this TTree tree, Action<TTree> each)
        where TTree : class, ITree<TTree>
    {
        return tree.ForEach(r => each(r as TTree)) as TTree;
    }

    #region Filter
    public static TTree Filter<TTree, TKey>(this TTree tree, Func<TTree, TKey> keySelector, params TKey[] keys)
        where TTree : class, ITree<TTree>
    {
        return tree.Filter(t => keySelector(t as TTree), keys) as TTree;
    }

    public static TTree Filter<TTree, TKey>(this TTree tree, Func<TTree, TKey> keySelector, IEnumerable<TKey> keys)
        where TTree : class, ITree<TTree>
    {
        return tree?.Filter(t => keySelector(t as TTree), keys) as TTree;
    }

    public static TTree Filter<TTree>(this TTree tree, Func<TTree, bool> predicate)
        where TTree : class, ITree<TTree>
    {
        return tree?.Filter(r => predicate(r as TTree)) as TTree;
    }

    public static TTree Filter<TTree, TKey>(this ITree<TTree, TKey> tree, params TKey[] keys)
        where TTree : class, ITree<TTree>, ITree<TTree, TKey>
        where TKey : class
    {
        return tree?.Filter(t => ((TTree)t).Id, keys) as TTree;
    }

    public static TTree Filter<TTree, TKey>(this ITree<TTree, TKey> tree, IEnumerable<TKey> keys)
        where TTree : class, ITree<TTree>, ITree<TTree, TKey>
        where TKey : class
    {
        return tree?.Filter(t => ((TTree)t).Id, keys) as TTree;
    }

    public static TTree Filter<TTree>(this ITree<TTree> tree, Func<ITree<TTree>, bool> predicate)
        where TTree : class, ITree<TTree>
    {
        return tree?.Filter(r => predicate(r)) as TTree;
    }

    public static TTree Filter<TTree, TKey>(this IStructTree<TTree, TKey> tree, params TKey[] keys)
        where TTree : class, ITree<TTree>, IStructTree<TTree, TKey>
        where TKey : struct
    {
        return tree?.Filter(t => ((TTree)t).Id, keys) as TTree;
    }

    public static TTree Filter<TTree, TKey>(this IStructTree<TTree, TKey> tree, IEnumerable<TKey> keys)
        where TTree : class, ITree<TTree>, IStructTree<TTree, TKey>
        where TKey : struct
    {
        return tree?.Filter(t => ((TTree)t).Id, keys) as TTree;
    }

    public static TTree Filter<TTree, TCode>(this ICodeTree<TTree, TCode> tree, params TCode[] codes)
        where TTree : class, ITree<TTree>, ICodeTree<TTree, TCode>
        where TCode : class
    {
        return tree?.Filter(t => ((TTree)t).Code, codes) as TTree;
    }

    public static TTree Filter<TTree, TCode>(this ICodeTree<TTree, TCode> tree, IEnumerable<TCode> codes)
        where TTree : class, ITree<TTree>, ICodeTree<TTree, TCode>
        where TCode : class
    {
        return tree?.Filter(t => ((TTree)t).Code, codes) as TTree;
    }
    #endregion

    public static List<TTree> ToList<TTree>(this TTree tree)
        where TTree : class, ITree<TTree>
    {
        var list = new List<TTree>();
        tree.ForEach(r => list.Add(r));
        return list;
    }

    #region AddRange
    public static IEnumerable<TTree> AddRange<TTree, TKey>(this TTree tree, IEnumerable<TTree> nodes, Func<TTree, TKey> idSelector, Func<TTree, TKey> parentIdSelector)
        where TTree : class, ITree<TTree>
    {
        var result = nodes.ToList();
        var groups = nodes.GroupBy(r => parentIdSelector(r));
        ForEach(tree, t =>
        {
            foreach (var group in groups)
            {
                if (group.Key.Equals(idSelector(t)))
                {
                    t.Children ??= new List<TTree>();
                    t.Children.AddRange(group);
                    foreach (var node in group)
                    {
                        result.Remove(node);
                    }
                }
            }
        });
        return result;
    }

    public static IEnumerable<TTree> AddRange<TTree, TKey>(this ITree<TTree, TKey> tree, IEnumerable<TTree> nodes)
        where TTree : class, ITree<TTree>, ITree<TTree, TKey>
        where TKey : class
    {
        var result = nodes.ToList();
        var groups = nodes.GroupBy(r => r.ParentId);
        tree.ForEach(t =>
        {
            foreach (var group in groups)
            {
                if (group.Key.Equals(((TTree)t).Id))
                {
                    t.Children ??= new List<TTree>();
                    t.Children.AddRange(group);
                    foreach (var node in group)
                    {
                        result.Remove(node);
                    }
                }
            }
        });
        return result;
    }

    public static IEnumerable<TTree> AddRange<TTree, TKey>(this ICodeTree<TTree, TKey> tree, IEnumerable<TTree> nodes)
        where TTree : class, ITree<TTree>, ICodeTree<TTree, TKey>
        where TKey : class
    {
        var result = nodes.ToList();
        var groups = nodes.GroupBy(r => r.ParentCode);
        tree.ForEach(t =>
        {
            foreach (var group in groups)
            {
                if (group.Key.Equals(((TTree)t).Code))
                {
                    t.Children ??= new List<TTree>();
                    t.Children.AddRange(group);
                    foreach (var node in group)
                    {
                        result.Remove(node);
                    }
                }
            }
        });
        return result;
    }

    public static IEnumerable<TTree> AddRange<TTree, TKey>(this IStructTree<TTree, TKey> tree, IEnumerable<TTree> nodes)
        where TTree : class, ITree<TTree>, IStructTree<TTree, TKey>
        where TKey : struct
    {
        var result = nodes.ToList();
        var groups = nodes.GroupBy(r => r.ParentId);
        tree.ForEach(t =>
        {
            foreach (var group in groups)
            {
                if (group.Key.Equals(((TTree)t).Id))
                {
                    t.Children ??= new List<TTree>();
                    t.Children.AddRange(group);
                    foreach (var node in group)
                    {
                        result.Remove(node);
                    }
                }
            }
        });
        return result;
    }
    #endregion

}
